Skip to content

Upgrade dependencies, update tools with new API features#21

Open
hi-rai wants to merge 1 commit intomainfrom
dev/himanshu/upgrade-libs-and-apis
Open

Upgrade dependencies, update tools with new API features#21
hi-rai wants to merge 1 commit intomainfrom
dev/himanshu/upgrade-libs-and-apis

Conversation

@hi-rai
Copy link
Copy Markdown

@hi-rai hi-rai commented Apr 23, 2026

  1. Upgrade all dependencies to latest versions
  2. Use fetch instead of axios
  3. Use registerTool with input/output zod schemas
  4. Update with new API features

There are some minor non-backward compatible changes in this PR.

  • create_test_case and update_test_case tool parameter renamed: projectId → projectCode
  • customFields.*.isDefault is now required (previously optional; refine allowed either value or isDefault)
  • Test-case step field rename in text output dropped: description / expected are no longer rewritten to action / expected_result
  • list_shared_preconditions text content shape changed: now {sharedPreconditions: [...]} (was a bare array)
  • list_custom_fields text content shape changed: now {customFields: [...]} (was a bare array)
  • Strict response-schema validation: apiQuery runs every successful response through Zod and throws on any mismatch (was lightweight presence checks)
  • Node >= 20 required (ES2022 + NodeNext + newer @types/node/typescript)

1. Upgrade all dependencies to latest versions
2. Use fetch instead of axios
3. Use registerTool with input/output zod schemas
4. Update with new API features
@hi-rai hi-rai self-assigned this Apr 23, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the MCP server to use a centralized apiQuery utility based on the native fetch API, replacing axios. It introduces a comprehensive set of Zod schemas for request/response validation and reconfigures the TypeScript environment to use NodeNext. While the refactoring improves type safety, several critical issues were identified: multiple dependency versions in package.json and the Biome schema appear to be hallucinated and do not exist. Additionally, the code uses invalid Zod methods like z.url() instead of z.string().url(), and recursive schemas are not properly wrapped in z.lazy(). There is also a significant concern regarding the use of server.registerTool, as the standard MCP SDK uses server.tool() and does not support the provided metadata structure, which will lead to runtime failures.

Comment thread package.json
Comment thread src/schemas.ts
Comment thread src/schemas.ts
Comment thread src/tools/customFields.ts
Comment thread biome.json
Comment thread src/tools/shared-steps.ts
@claude
Copy link
Copy Markdown

claude Bot commented Apr 23, 2026

Code Review — PR #21: Upgrade dependencies, update tools with new API features

Overall this is a well-structured modernisation PR. The centralised api.ts, the move to registerTool with I/O schemas, and the native-fetch migration all move in the right direction. A few items need attention before merging.


Overview

Area Change
MCP SDK ^1.8.0^1.29.0
HTTP client axios removed, replaced with fetch
Zod ^3.22.4^4.3.6 (major)
TypeScript ^5.8.2^6.0.3 (major)
Testing vitest removed — zero tests remain
Tool registration server.tool()server.registerTool() with Zod I/O schemas
Node requirement >= 20 (new floor)

🔴 Critical

1. Entire test suite deleted with no replacement

src/utils.test.ts is deleted alongside vitest, and the test script is removed from package.json. The codebase now has no automated test coverage at all. The JSONStringify utility being deleted is fine, but the testing infrastructure going with it is a significant regression. Even a minimal smoke test for apiQuery's error paths and the buildQueryString helper would be worthwhile. Please add tests (or at minimum re-add vitest) before merging.


🟠 Bugs / Correctness

2. Schema-validation ApiError carries the wrong status code

In src/api.ts:

const parsed = opts.schema.safeParse(raw)
if (!parsed.success) {
  throw new ApiError(`Response validation failed: ${parsed.error.message}`, res.status, raw)
}

This path is only reached when res.ok is true, so res.status is a 2xx code. Every downstream catch block then checks error.status === 4xx and misses this case, falling through to the generic throw new Error(...). Callers will see a confusing message like "Response validation failed: ... (200)". Consider using a dedicated sentinel (e.g. 0 or 422) or a separate error class so callers can distinguish validation failures from HTTP errors.

3. list_shared_preconditions validates against a sub-schema, not the full output schema

const sharedPreconditions = await apiQuery(
  `/api/public/v0/project/${projectCode}/shared-precondition`,
  {
    schema: listSharedPreconditionsOutputSchema.shape.sharedPreconditions,  // ← array schema
    ...
  }
)
const result = { sharedPreconditions }

The raw API returns an array, but the output schema wraps it in { sharedPreconditions: [...] }. Passing .shape.sharedPreconditions works around this, but it means structuredContent validation against outputSchema (done by the SDK) will fail because the returned result shape differs from the declared outputSchema. Either align the API expectation with the schema, or adjust the output schema to match what the API actually returns.

4. list_folders error handler silently swallows 401/403

All other tools explicitly re-throw user-friendly messages for 401/403. list_folders only handles 404:

} catch (error: unknown) {
  if (error instanceof ApiError) {
    if (error.status === 404) {
      throw new Error(`Project with code '${projectCode}' not found.`)
    }
    throw new Error(`Failed to fetch test case folders: ${error.message}`)  // 401/403 land here
  }

An auth failure gives "Failed to fetch test case folders: Unauthorized" rather than the standard "Invalid or missing API key" message. Consistent with other tools, this should handle 401/403 explicitly.


🟡 Design / Maintainability

5. Duplicated error-handling pattern across every tool

Every tool file implements the same if (error instanceof ApiError) { if (error.status === 401) throw ... } block. With 10+ tools this is hard to keep consistent (as issue #4 above shows). Consider a shared helper:

function rethrowApiError(error: unknown, context: string, projectCode?: string): never {
  if (error instanceof ApiError) {
    if (error.status === 401) throw new Error('Invalid or missing API key')
    if (error.status === 403) throw new Error('Insufficient permissions or suspended tenant')
    if (error.status === 404 && projectCode)
      throw new Error(`Project with code '${projectCode}' not found.`)
    throw new Error(`${context}: ${error.message}`)
  }
  throw error
}

6. testStepSchemaShape getter creates a new Zod schema object on every access

export const testStepSchemaShape = {
  // ...
  get subSteps() {
    return z.array(testStepSchema).optional().describe('...')
  },
}

The getter is needed to break the circular reference, but it constructs a new schema instance every time testStepSchemaShape.subSteps is read (e.g. when building the full tool I/O schema at startup). Cache it after first creation, or construct the lazy reference directly inside testStepSchema using z.lazy():

const testStepSchema: z.ZodType<TestStep> = z.object({
  ...
  subSteps: z.lazy(() => z.array(testStepSchema)).optional(),
})

7. Content-Type: application/json sent on GET requests

const headers: Record<string, string> = {
  Authorization: `ApiKey ${QASPHERE_API_KEY}`,
  'Content-Type': 'application/json',  // unnecessary for GET
}

Harmless but semantically incorrect. Consider only setting it when opts.body !== undefined.


🟢 Positive observations

  • Centralising all HTTP logic in src/api.ts is a clear win — much easier to add retries, timeouts, or logging in one place.
  • Zod I/O schemas on every tool are a great improvement; MCP clients can now introspect tool contracts.
  • Replacing the ad-hoc field presence checks with proper Zod parsing is strictly better than what was there before.
  • buildQueryString correctly handles undefined values and arrays — clean implementation.
  • Removing types.ts and deriving types from Zod schemas eliminates the drift risk between the two.
  • Breaking changes are clearly enumerated in the PR description — good migration documentation.

Summary

The architecture is solid. The three items that should be addressed before merging:

  1. Add back a test suite (at minimum re-add vitest and cover apiQuery error paths).
  2. Fix the ApiError status code for schema-validation failures.
  3. Fix the list_shared_preconditions schema mismatch (sub-schema vs output schema).

The duplicated error handler and the Content-Type/getter issues are worth cleaning up but are not blockers.

Comment thread package.json
"dev": "tsx src/index.ts",
"lint": "biome lint --write .",
"format": "biome format --write .",
"check": "biome check src",
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check does lint+format in the same command

Comment thread package.json
"lint-staged": "^15.5.1",
"tsx": "^4.19.3",
"typescript": "^5.8.2",
"vitest": "^3.1.1"
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no tests, so removed vitest

Comment thread src/schemas.ts
Comment on lines +110 to +112
type: z
.string()
.describe('Type of the step. Known values: "standalone" | "shared" | "shared_sub_step"'),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally didn't keep any enums in output schemas, so things don't break if we add new values in the backend

Comment thread package.json
"typescript": "^5.8.2",
"vitest": "^3.1.1"
"lint-staged": "^16.4.0",
"tsx": "^4.21.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node can run typescript natively now, this may not be necessary anymore

Comment thread package.json
"dotenv": "^16.4.5",
"zod": "^3.22.4"
"@modelcontextprotocol/sdk": "^1.29.0",
"dotenv": "^17.4.2",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is dotenv used?

Comment thread tsconfig.json
Comment on lines -3 to +4
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"target": "ES2022",
"module": "NodeNext",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typescript 6 defaults to a roaming (always latest) target and module versions, consider not pinning them here

Comment thread tsconfig.json
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also default in ts 6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants